package org.dromara.common.encrypt.interceptor

import cn.hutool.core.collection.CollUtil
import cn.hutool.core.convert.Convert
import io.github.oshai.kotlinlogging.KotlinLogging
import org.apache.ibatis.executor.parameter.ParameterHandler
import org.apache.ibatis.plugin.Interceptor
import org.apache.ibatis.plugin.Intercepts
import org.apache.ibatis.plugin.Invocation
import org.apache.ibatis.plugin.Signature
import org.dromara.common.core.utils.StringUtils
import org.dromara.common.encrypt.annotation.EncryptField
import org.dromara.common.encrypt.core.EncryptContext
import org.dromara.common.encrypt.core.EncryptorManager
import org.dromara.common.encrypt.enumd.AlgorithmType
import org.dromara.common.encrypt.enumd.EncodeType
import org.dromara.common.encrypt.properties.EncryptorProperties
import java.lang.reflect.Field
import java.sql.PreparedStatement
import java.util.*

/**
 * 入参加密拦截器
 *
 * @author 老马
 * @version 4.6.0
 * @updater LikeYouDo
 * @date 2025/1/14 13:56
 */
@Intercepts(Signature(type = ParameterHandler::class, method = "setParameters", args = [PreparedStatement::class]))
open class MybatisEncryptInterceptor(
    private val encryptorManager: EncryptorManager,
    private val defaultProperties: EncryptorProperties,
) : Interceptor {
    companion object {
        private val log = KotlinLogging.logger { }
    }

    @Throws(Throwable::class)
    override fun intercept(invocation: Invocation): Any {
        return invocation
    }

    override fun plugin(target: Any): Any {
        if (target is ParameterHandler) {
            // 进行加密操作
            val parameterObject = target.parameterObject
            if (parameterObject != null && parameterObject !is String) {
                this.encryptHandler(parameterObject)
            }
        }
        return target
    }

    /**
     * 加密对象
     *
     * @param sourceObject 待加密对象
     */
    private fun encryptHandler(sourceObject: Any?) {
        if (sourceObject == null) {
            return
        }
        if (sourceObject is Map<*, *>) {
            HashSet(sourceObject.values).forEach(this::encryptHandler)
            return
        }
        if (sourceObject is List<*>) {
            if (CollUtil.isEmpty(sourceObject)) {
                return
            }
            // 判断第一个元素是否含有注解。如果没有直接返回，提高效率
            val firstItem = sourceObject[0]
            if (firstItem == null || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.javaClass))) {
                return
            }
            sourceObject.forEach(this::encryptHandler)
            return
        }
        // 不在缓存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错)
        val fields = encryptorManager.getFieldCache(sourceObject.javaClass) ?: return
        try {
            for (field in fields) {
                field[sourceObject] = encryptField(Convert.toStr(field[sourceObject]), field)
            }
        } catch (e: Exception) {
            log.error(e) { "处理加密字段时出错" }
        }
    }

    /**
     * 字段值进行加密。通过字段的批注注册新的加密算法
     *
     * @param value 待加密的值
     * @param field 待加密字段
     * @return 加密后结果
     */
    private fun encryptField(value: String?, field: Field): String? {
        if (value == null) {
            return null
        }
        val encryptField = field.getAnnotation(EncryptField::class.java)
        val encryptContext = EncryptContext().apply {
            algorithm =
                if (encryptField.algorithm == AlgorithmType.DEFAULT) defaultProperties.algorithm else encryptField.algorithm
            encode =
                if (encryptField.encode == EncodeType.DEFAULT) defaultProperties.encode else encryptField.encode
            password =
                if (StringUtils.isBlank(encryptField.password)) defaultProperties.password else encryptField.password
            privateKey =
                if (StringUtils.isBlank(encryptField.privateKey)) defaultProperties.privateKey else encryptField.privateKey
            publicKey =
                if (StringUtils.isBlank(encryptField.publicKey)) defaultProperties.publicKey else encryptField.publicKey
        }
        return encryptorManager.encrypt(value, encryptContext)
    }

    override fun setProperties(properties: Properties?) {
    }

}
